package core.experiment;

import java.util.ArrayList;
import java.util.List;

import core.algorithms.SearchParameters;
import core.algorithms.SearchResults;
import core.course.TimeTable;
import core.interfaces.IProgressListener;
import core.interfaces.ISearchAlgorithm;

public class Experiment
{
	public static enum SearchParamToControl
	{
		NONE,
		COURSE_NUMBER,
		MAX_STEPS,
		RANDOM_STEPS,
		DRAW_LIMIT,
	};
	private static int STATIC_EXPERIMENT_ID = 0;
	public static synchronized int getUniqueId()
	{
		return STATIC_EXPERIMENT_ID++;
	}
	
	
	public static List<Experiment> getExperiments(
			ISearchAlgorithm<TimeTable> searchAlgorithm,
			int initialSeed,
			int totalExperiments,
			SearchParamToControl searchParamToControl,
			int incrementIterations,
			int incrementStep,
			int experimentId,
			boolean bindDrawsCourses,
			double percentageDrawsCourses)
			
			{
				ArrayList<Experiment> retList = new ArrayList<Experiment>();
				switch (searchParamToControl)
				{
				case NONE:
					retList.add(new Experiment(searchAlgorithm, initialSeed, totalExperiments, experimentId));
					setBindingDrawsCourses(bindDrawsCourses, percentageDrawsCourses, searchAlgorithm);
					break;
				default:
					List<IProgressListener> originalProgressListeners = new ArrayList<IProgressListener>(searchAlgorithm.getProgressListeners());
					searchAlgorithm.getProgressListeners().clear();
					int controledVarValue = getControledVarValue(searchParamToControl, searchAlgorithm.getSearchParameters()); 
						searchAlgorithm.getSearchParameters().getCourseNumber();
					for (int i = 0; i < incrementIterations; i+=incrementStep)
					{
						ISearchAlgorithm<TimeTable> clonedAlgorithm = searchAlgorithm.clone();
						clonedAlgorithm.setProgressListeners(originalProgressListeners);
						setControledVarValue(controledVarValue + i, searchParamToControl, clonedAlgorithm.getSearchParameters());
						retList.add(new Experiment(clonedAlgorithm, initialSeed, totalExperiments, experimentId));
						setBindingDrawsCourses(bindDrawsCourses, percentageDrawsCourses, clonedAlgorithm);
					}
				
					
					break;
				}
				return retList;
			}
	
	private static void setBindingDrawsCourses(
			boolean bindDrawsCourses,
			double percentageDrawsCourses,
			ISearchAlgorithm<TimeTable> searchAlgorithm)
	{
		if (bindDrawsCourses)
		{
			int drawsNumber = (int)Math.round(searchAlgorithm.getSearchParameters().getCourseNumber() * 0.01 * percentageDrawsCourses);
			searchAlgorithm.getSearchParameters().setDrawLimit(drawsNumber);
		}
	}
	
	private static void setControledVarValue(
			int value,
			SearchParamToControl searchParamToControl,
			SearchParameters<TimeTable> searchParameters)
	{
		switch (searchParamToControl)
		{
			case COURSE_NUMBER:
				searchParameters.setCourseNumber(value);
				break;
			case DRAW_LIMIT:
				searchParameters.setDrawLimit(value);
				break;
			case MAX_STEPS:
				searchParameters.setMaximumHeuristicCalls(value);
				break;
			case RANDOM_STEPS:
				searchParameters.setRandomSteps(value);
				break;
			default:
				break;
		}
	}
	private static int getControledVarValue(SearchParamToControl searchParamToControl, SearchParameters<TimeTable> searchParameters)
	{
		int value = 0;
		switch (searchParamToControl)
		{
			case COURSE_NUMBER:
				value = searchParameters.getCourseNumber();
				break;
			case DRAW_LIMIT:
				value = searchParameters.getDrawLimit();
				break;
			case MAX_STEPS:
				value = searchParameters.getMaximumHeuristicCalls();
				break;
			case RANDOM_STEPS:
				value = searchParameters.getRandomSteps();
				break;
			default:
				break;
		}
		return value;
	}


	public Experiment(ISearchAlgorithm<TimeTable> searchAlgorithm,
			int initialSeed, int totalExperiments, int experimentId)
	{
		super();
		
		// assume only one (since paramToControl = None);
		m_SearchAlgorithm = searchAlgorithm;
		m_InitialSeed = initialSeed;
		m_TotalExperiments = totalExperiments;
//		m_OriginalParameters = searchAlgorithm.getSearchParameters().clone();
		m_ExperimentId = experimentId;
	}
	
	private int m_ExperimentId;
	private ISearchAlgorithm<TimeTable> m_SearchAlgorithm = null;
	private int m_InitialSeed;
	private int m_TotalExperiments;
	private boolean m_IsFinished = false;
//	private SearchParameters<TimeTable> m_OriginalParameters = null;
	private ExperimentResults m_ExperimentResults = null;
	private IProgressListener m_IProgressListener = null;
//	private boolean m_BindDrawsCourses = false;
//	private double m_PercentageDrawsCourse = 20.0;	
	public void addProgressListener(IProgressListener progressListener)
	{
		m_IProgressListener = progressListener;
		
	}

	public int getExperimentId()
	{
		return m_ExperimentId;
	}
	
	public String getAlgorithmName()
	{
		return m_SearchAlgorithm.getAlgorithmName();
	}

	public SearchParameters<TimeTable> getSearchParameters()
	{
		return m_SearchAlgorithm.getSearchParameters();
		//return m_OriginalParameters;
	}

	public ExperimentResults getSearchResults()
	{
		return m_ExperimentResults;
	}

	public boolean isFinished()
	{
		return m_IsFinished;
	}

	public void start()
	{
		int initialSeed = m_SearchAlgorithm.getSearchParameters().getSpaceSeed();
		
		double 	minValue = Double.POSITIVE_INFINITY;
		double 	maxValue = Double.NEGATIVE_INFINITY;
		double 	sumfinalValue = 0.0;
		int 	sumCourseNumberFinalState = 0;
		int 	counterFinishAllSteps = 0;
		int 	sumStepsTaken = 0;
		int 	courseNumberPerExp = m_SearchAlgorithm.getSearchParameters().getCourseNumber();
		for (int seed = initialSeed, i = 0; i < m_TotalExperiments; i++, seed++)
		{
			SearchParameters<TimeTable> searchParameters = m_SearchAlgorithm.getSearchParameters();
			searchParameters.setSpaceSeed(seed);
			searchParameters.setInitialState(
					TimeTable.getEmptyInitialStateRandomCourses(courseNumberPerExp, seed));
			m_SearchAlgorithm.initAlgorithm(searchParameters);
			m_SearchAlgorithm.start();
			SearchResults<TimeTable> results = m_SearchAlgorithm.getSearchResults();
			double finalValue = results.m_FinalStateValue;
			if (minValue > finalValue)
			{
				minValue = finalValue;
			}
			if (maxValue < finalValue)
			{
				maxValue = finalValue;
			}
			sumfinalValue += finalValue;
			sumCourseNumberFinalState += results.getFinalState().getState().size();
			if (results.isFinishedAllSteps())
			{
				counterFinishAllSteps ++;
			}
			sumStepsTaken += results.getTotalStepsTaken();
			m_IProgressListener.percentageComplete(getPercentageComplete(i + 1), 1);
		}
		
		// calculate results
		ExperimentResults experimentResults = new ExperimentResults();
		experimentResults.setAverageCourseNumber(
				(double)sumCourseNumberFinalState / (double) m_TotalExperiments);
		experimentResults.setAverageFinalValue(
				sumfinalValue / (double) m_TotalExperiments);
		experimentResults.setAverageTotalStepsTaken(
				(double)sumStepsTaken / (double) m_TotalExperiments);
		experimentResults.setMaximumFinalValue(maxValue);
		experimentResults.setMinimumFinalValue(minValue);
		experimentResults.setPercentageFinishedAllSteps(
				(double)counterFinishAllSteps / (double) m_TotalExperiments);
		
		m_ExperimentResults = experimentResults;
		m_IsFinished = true;
			
	}

	private double getPercentageComplete(int finished)
	{
		double value = (100.0 * finished) / (double)m_TotalExperiments;
		return value;
	}
	
	public String getSpaceSeedRange()
	{
		return String.format("[%d-%d]", m_InitialSeed, m_InitialSeed + m_TotalExperiments );
	}
}

